#ifndef TEST_PERF_H_
#define TEST_PERF_H_

#include "../Utils/CycleTimer.h"

class TestPerfAdder {
    const int THREAD_NUM;
    const unsigned int LIMIT;
private:
    void testSpeedOfNonAtomicAndLockBasedAdd() {
        CycleTimer timer;
        unsigned int sum;
        timer.Start();
        sum = 0;
#pragma omp parallel
        {
            unsigned int localSum = 0;
            for (unsigned int i = 0; i < LIMIT; ++i) {
                ++localSum;
            }
#pragma omp atomic
            sum += localSum;
        }
        timer.Stop();
        assert(sum == LIMIT * THREAD_NUM);
        printf("testSpeedOfNonAtomicAndLockBasedAdd\n\ttime=%.6lf\n\n", timer.GetSecond());
    }

    void testSpeedOfAtomicBasedAdd() {
        CycleTimer timer;
        unsigned int sum;
        timer.Start();
        sum = 0;
#pragma omp parallel
        {
            for (unsigned int i = 0; i < LIMIT; ++i) {
#pragma omp atomic
                ++sum;
            }
        }
        timer.Stop();
        assert(sum == LIMIT * THREAD_NUM);
        printf("testSpeedOfAtomicBasedAdd\n\ttime=%.6lf\n\n", timer.GetSecond());
    }

    void testSpeedOfLockBasedAdd() {
        CycleTimer timer;
        unsigned int sum;
        omp_lock_t lock;
        omp_init_lock(&lock);
        timer.Start();
        sum = 0;
#pragma omp parallel
        {
            for (unsigned int i = 0; i < LIMIT; ++i) {
                omp_set_lock(&lock);
                ++sum;
                omp_unset_lock(&lock);
            }
        }
        timer.Stop();
        omp_destroy_lock(&lock);
        assert(sum == LIMIT * THREAD_NUM);
        printf("testSpeedOfLockBasedAdd\n\ttime=%.6lf\n\n", timer.GetSecond());
    }

public:
    TestPerfAdder(const int THREAD_NUM, const unsigned int LIMIT = 0x0000ffffu) :
            THREAD_NUM(THREAD_NUM), LIMIT(LIMIT) {
    }

    void testSpeedOfAtomicAndLockBasedAdd() {
        omp_set_num_threads(THREAD_NUM);
        for (int i = 0; i < 5; ++i)
            testSpeedOfNonAtomicAndLockBasedAdd();
        for (int i = 0; i < 5; ++i)
            testSpeedOfAtomicBasedAdd();
        for (int i = 0; i < 5; ++i)
            testSpeedOfLockBasedAdd();
    }
};

class TestCorePerf {
    const int n;
    int * const origBuf;
    int * buf;
private:
    static void qsort(int *buf, int h, int t) {
        int i = h, j = t, x = buf[(i + j) / 2];
        while (i <= j) {
            while (i <= j && buf[i] < x)
                ++i;
            while (i <= j && x < buf[j])
                --j;
            if (i <= j) {
                swap(buf[i], buf[j]);
                ++i;
                --j;
            }
        }
        if (i < t) qsort(buf, i, t);
        if (h < j) qsort(buf, h, j);
    }
public:
    TestCorePerf() :
            n(0x1000000), origBuf((int*) malloc(sizeof(int) * 2 * n)) {
        assert(origBuf != NULL);
        for (int i = 0; i < n; ++i) {
            origBuf[i] = rand();
        }
        buf = origBuf + n;
    }

    ~TestCorePerf() {
        free(origBuf);
    }

    void reset() {
        memcpy(buf, origBuf, sizeof(int) * n);
    }

    void testSpeed(const char * const NAME) {
        reset();
        CycleTimer timer;
        timer.Start();
        qsort(buf, 0, n - 1);
        timer.Stop();
        int x = 0;
        for (int i = 0; i < n; ++i)
            x ^= buf[i];
        printf("testSpeed NAME=%s x=%08x\n\ttime=%.6lf\n\n", NAME, x, timer.GetSecond());
    }
};

class TestPerf {
public:
    static void testPerfAdder() {
#ifndef IS_MIC
        TestPerfAdder testPerfAdder(8);
        testPerfAdder.testSpeedOfAtomicAndLockBasedAdd();
#else
        TestPerfAdder testPerfAdder(120);
        testPerfAdder.testSpeedOfAtomicAndLockBasedAdd();
#endif
    }

    static void testPerfCoreSpeed() {
        TestCorePerf testCorePerf;
#ifndef IS_MIC
        for (int i = 0; i < 5; ++i) {
            testCorePerf.testSpeed("CPU");
        }
#else
        for (int i = 0; i < 5; ++i) {
            testCorePerf.testSpeed("MIC");
        }
#endif
    }
};

#endif
